#include "JSONHandler.h"
#include <filesystem> // Required for isValidFilePath with C++17
#include <fstream>    // Required for isValidFilePath without C++17 and file operations
#include <stdexcept>  // For exception handling
#include <sstream>    // Needed for splitting strings
#include <vector>     // Needed for storing path segments
#include "RandomProvider/RandomProvider.h"


static  RandomProvider* t_randomProvider = nullptr;
// Helper function (can stay here or move to utility header)
bool isValidFilePath(const std::string& path) {
    try {
        return std::filesystem::exists(path) && !std::filesystem::is_directory(path);
    } catch (const std::filesystem::filesystem_error& e) {
        std::cerr << "Filesystem error checking path '" << path << "': " << e.what() << std::endl;
        return false;
    }
}

// Helper function to split string by delimiter
static std::vector<std::string> splitPath(const std::string& s, char delimiter) {
    std::vector<std::string> tokens;
    std::string token;
    std::istringstream tokenStream(s);
    while (std::getline(tokenStream, token, delimiter)) {
        tokens.push_back(token);
    }
    return tokens;
}

// --- Constructors ---
JSONHandler::JSONHandler() : m_isDirty(false), m_dataVersion(0) {
    // Initialize random provider only if C++17 features are used
    try {
         t_randomProvider = &RandomProvider::getInstance();
    } catch (const std::exception& e) {
        std::cerr << "Warning: Failed to create RandomProvider in JSONHandler constructor: " << e.what() << std::endl;
        t_randomProvider = nullptr;
    } catch (...) {
        std::cerr << "Warning: Unknown error creating RandomProvider in JSONHandler constructor." << std::endl;
        t_randomProvider = nullptr;   
    }
}

JSONHandler::JSONHandler(const std::string& filepath) : JSONHandler() { // Delegate to default constructor
    loadFromJsonFile(filepath);
}

// --- Data Management --- 
bool JSONHandler::init(const std::string* source) {
    clear(); // Clear previous state first (increments version)
    if (source == nullptr || source->empty()) {
        std::cerr << "JSONHandler::init Error: empty source string." << std::endl;
        return false;
    }

    // Check if it is an available file path
    if (isValidFilePath(*source)) {
        if (loadFromJsonFileInternal(*source)) { // Use internal loader
            m_isDirty = false; 
            // The version incremented by clear() is the correct version for this new state.
            return true;
        } else {
            // loadFromJsonFile already prints error
            return false; 
        }
    }

    // If not a valid path, try parsing as a JSON string
    try {
        m_jsonObject = nlohmann::json::parse(*source);
        // Check if the JSON object is an actual object and save the first key
        if (m_jsonObject.is_object() && !m_jsonObject.empty()) {
            setMainKey(m_jsonObject.begin().key());
            m_currentKey = this->m_mainKey;
        } else {
             // Handle cases where JSON is valid but not an object (e.g., array, primitive)
             m_mainKey.clear();
             m_currentKey.clear();
        }
        m_currentfile.clear(); // Parsed from string, not file
        m_isDirty = false; // Successfully parsed
        // The version incremented by clear() is the correct version for this new state.
        return true; 
    } catch (const nlohmann::json::parse_error& e) {
        std::cerr << "JSONHandler::init JSON parse error: " << e.what() << std::endl;
        // m_jsonObject should have been cleared by the initial clear() call
        return false;
    } catch (const std::exception& e) {
        std::cerr << "JSONHandler::init Error parsing JSON string: " << e.what() << std::endl;
        // m_jsonObject should have been cleared by the initial clear() call
        return false;
    } catch (...) {
        std::cerr << "JSONHandler::init Unknown exception while parsing JSON string." << std::endl;
        // m_jsonObject should have been cleared by the initial clear() call
        return false;
    }
}

bool JSONHandler::reset(const std::string* source) noexcept {
    // `init` calls `clear` which increments version.
    try {
        // init will ensure the correct (incremented) version is set on success.
        return init(source);
    } catch (...) {
        // Prevent exceptions from propagating (noexcept)
        std::cerr << "JSONHandler::reset caught an unexpected exception during init." << std::endl;
        return false;
    }
}

void JSONHandler::clear() noexcept {
    // Removed map clears as they are gone
    m_jsonObject.clear();
    m_currentfile.clear();
    m_mainKey.clear();
    m_currentKey.clear();
    m_isDirty = false; // Clearing resets dirty state
    ++m_dataVersion; // Increment version on clear
    // Note: Does not clear t_randomProvider if it exists
}

bool JSONHandler::loadFromJsonFileInternal(const std::string& inputFileName) {
    try {
        std::ifstream jsonFile(inputFileName);
        if (!jsonFile.is_open()) { // Check if file opened successfully
            std::cerr << "JSONHandler::loadFromJsonFile Error: Failed to open file '" << inputFileName << "'" << std::endl;
            return false;
        }
        
        // Clear previous state - DON'T call clear() here as it resets version prematurely
        m_jsonObject.clear();
        m_currentfile.clear();
        m_mainKey.clear();
        m_currentKey.clear();
        m_isDirty = false; 
        // Version is handled by the caller (init/loadFromJsonFile) based on whether state changed
        // No version reset here.

        jsonFile >> m_jsonObject;
        m_currentfile = inputFileName;

        // Check if the JSON object is an actual object and save the first key
        if (m_jsonObject.is_object() && !m_jsonObject.empty()) {
            setMainKey(m_jsonObject.begin().key());
            m_currentKey = this->m_mainKey; // Assuming currentKey should track mainKey initially
        }
         m_isDirty = false; // Freshly loaded

    } catch (const nlohmann::json::parse_error& e) {
        std::cerr << "JSONHandler::loadFromJsonFile JSON parse error in file '" << inputFileName << "': " << e.what() << std::endl;
        // Don't call clear() here, let caller handle cleanup if needed
        m_jsonObject.clear(); // Clear partial data
        return false;
    } catch (const std::exception& e) {
        std::cerr << "JSONHandler::loadFromJsonFile Error reading file '" << inputFileName << "': " << e.what() << std::endl;
        m_jsonObject.clear();
        return false;
    } catch (...) {
        std::cerr << "JSONHandler::loadFromJsonFile Unknown exception while reading file '" << inputFileName << "'" << std::endl;
        m_jsonObject.clear();
        return false;
    }
    return true;
}

bool JSONHandler::loadFromJsonFile(const std::string& inputFileName) {
    // Try loading internally. loadFromJsonFileInternal itself doesn't change version.
    // The change in state *is* the load itself. If it succeeds, the version
    // should reflect this new state. If clear() was called internally or if
    // this load represents a change from a previous state, the version
    // from clear() or previous modifications should persist or increment.
    // We rely on the increment happening during the modification steps (like clear called by init, or direct modifications).
    // Therefore, do not reset the version here.
    if (loadFromJsonFileInternal(inputFileName)) {
        m_isDirty = false; 
        return true;
    } else {
        // Clear might be called here to ensure clean state on failure.
        // If clear() increments version, failure path also increments version.
        // Let's stick to the current logic: clear() only called by init or explicitly.
        // Ensure state is cleared without necessarily incrementing version again if load failed.
        // Let's adjust the `else` block slightly if needed, but primarily remove the reset on success.
        
        // Current public loadFromJsonFile doesn't call clear() on failure,
        // let's keep it that way unless a different behavior is desired.
        // The main point is removing the version reset on success.
        return false; 
    }
}

// --- Save Implementation ---
bool JSONHandler::saveToFileImpl(const std::string& outputFileName, uint8_t dumpValue, const nlohmann::json &outputjson) {
    try {
        std::ofstream outputFile(outputFileName);
        if (!outputFile.is_open()) {
            std::cerr << "JSONHandler::saveToFile Error: Failed to open file for writing: '" << outputFileName << "'" << std::endl;
            return false;
        }
        // It's okay to save non-object JSON (arrays, primitives)
        // else if(outputjson.is_null()){ // Allow saving null JSON explicitly if needed
        //   std::cerr << "json object is null" << std::endl;
        //   return false;
        // }

        outputFile << outputjson.dump(dumpValue);
        if(outputFile.fail()) { // Check stream state after writing
             std::cerr << "JSONHandler::saveToFile Error: Failed to write JSON data to file '" << outputFileName << "'." << std::endl;
             return false;
        }

    } catch (const nlohmann::json::exception& e) {
        // Exceptions during dump() are possible (e.g., invalid UTF-8)
        std::cerr << "JSONHandler::saveToFile JSON dump exception: " << e.what() << std::endl;
        return false;
    } catch (const std::exception& e) {
        std::cerr << "JSONHandler::saveToFile Exception caught: " << e.what() << std::endl;
        return false;
    } catch (...) {
        std::cerr << "JSONHandler::saveToFile Unknown exception caught." << std::endl;
        return false;
    }
    return true;
}

bool JSONHandler::saveToFile(const std::string& outputFileName, uint8_t dumpValue) {
    if (saveToFileImpl(outputFileName, dumpValue, m_jsonObject)) {
        m_isDirty = false; // Mark as clean after successful save
        // DO NOT reset m_dataVersion here. Version reflects the state saved.
        return true;
    }
    return false;
}


// --- Creation ---

// Overload to accept nlohmann::json directly
bool JSONHandler::createConfigJsonFile(const std::string& path, const nlohmann::json& configJson) {
    // Directly use the internal save implementation with the provided json object
    return saveToFileImpl(path, 4, configJson);
}

// --- Implementation of the new function ---
bool JSONHandler::createConfigFromPathMap(const std::string& path, const std::map<std::string, std::string>& configMap) {
    nlohmann::json configJson = nlohmann::json::object(); // Start with an empty object

    for (const auto& pair : configMap) {
        const std::string& fullPath = pair.first;
        const std::string& valueStr = pair.second;

        std::vector<std::string> pathSegments = splitPath(fullPath, '.');

        if (pathSegments.empty()) {
            std::cerr << "Warning: Skipping empty path key in createConfigFromPathMap." << std::endl;
            continue;
        }

        nlohmann::json* currentLevel = &configJson;

        // Traverse or create the path except for the last segment
        for (size_t i = 0; i < pathSegments.size() - 1; ++i) {
            const std::string& segment = pathSegments[i];
            if (!currentLevel->contains(segment)) {
                // Create a new object if the key doesn't exist
                (*currentLevel)[segment] = nlohmann::json::object();
            } else if (!(*currentLevel)[segment].is_object()) {
                // Error: trying to traverse into a non-object (e.g., a string or number)
                std::cerr << "Error: Path conflict in createConfigFromPathMap. Key '" << segment
                          << "' in path '" << fullPath << "' exists but is not an object." << std::endl;
                return false; // Cannot proceed
            }
             currentLevel = &(*currentLevel)[segment]; // Move deeper
        }

        // Set the value at the final segment
        const std::string& finalSegment = pathSegments.back();
         if (currentLevel->contains(finalSegment) && !currentLevel->is_object()) {
             // Check if the final key already exists and is not an object itself (we want to overwrite a primitive/array, not traverse into an object)
              std::cerr << "Warning: Overwriting existing non-object value at path '" << fullPath << "' in createConfigFromPathMap." << std::endl;
         }
        // Assign the value (as string, could add logic here to auto-detect numbers/bools if needed)
        (*currentLevel)[finalSegment] = valueStr;
    }

    // Use the internal save implementation
    return saveToFileImpl(path, 4, configJson); // Using indent 4 for readability
}

// --- Accessors Implementation ---
JSONNode JSONHandler::nodeAccessor() const {
    // Const version: pass non-const reference via const_cast, but empty notifier
    return JSONNode(const_cast<nlohmann::json&>(m_jsonObject), {}); 
}

// Non-const version: pass non-const reference and the notification callback
JSONNode JSONHandler::nodeAccessor() {
    // Create a lambda that captures this and calls notifyDataModified
    ModificationNotifier notifier = [this]() { this->notifyDataModified(); };
    return JSONNode(m_jsonObject, std::move(notifier));
}

// Pass non-const json, const version ref, and the notification callback
JsonFlatMapAccessor JSONHandler::flatMapAccessor() {
    ModificationNotifier notifier = [this]() { this->notifyDataModified(); };
    // Pass json ref, const ref to dataVersion, and the notifier lambda
    return JsonFlatMapAccessor(m_jsonObject, m_dataVersion, std::move(notifier));
}

std::string JSONHandler::getJsonttoStdstring(int indent) const {
    // No lock needed if nlohmann::json::dump() is thread-safe for const objects
    try {
        // Pass indent parameter to dump()
        // indent = -1 means no indentation (compact)
        // indent >= 0 specifies the number of spaces for indentation
        return m_jsonObject.dump(indent);
    } catch (const nlohmann::json::type_error& e) {
        // Handle error, maybe log it
        std::cerr << "Error dumping JSON to string: " << e.what() << std::endl;
        return "{}"; // Return empty object string on error
    }
}


// --- C++17 Feature Implementations ---

void JSONHandler::createRNGJsonFile(const std::string& path, std::size_t Max_Size_byte, int MaxDepth) {
    if (!t_randomProvider) {
        std::cerr << "JSONHandler::createRNGJsonFile Error: RandomProvider not initialized." << std::endl;
        t_randomProvider = &RandomProvider::getInstance();
         if (!t_randomProvider) {  return; } // Still failed
    }

    nlohmann::json outputJson;
    outputJson["RandomMainKey"] = nlohmann::json::object();
    nlohmann::json* currentRoot = &outputJson["RandomMainKey"];
    std::size_t currentSize = outputJson.dump().size(); 
    int currentMaxDepth = 0;
    int itemCounter = 0;

    while (currentSize < Max_Size_byte) {
        int targetDepth = 0;
        if (currentMaxDepth >= MaxDepth) {
            targetDepth = t_randomProvider->getRandomValue<int>({0, MaxDepth}); // Stay within max depth
        } else {
            targetDepth = t_randomProvider->getRandomValue<int>({0, currentMaxDepth + 1}); // Allow increasing depth
            if (targetDepth > currentMaxDepth) {
                currentMaxDepth = targetDepth;
            }
        }

        // Get a parent object at the target depth, creating if necessary
        std::optional<std::reference_wrapper<nlohmann::json>> parentOpt = getTargetLevel(currentRoot, targetDepth);
        if (!parentOpt) { 
             std::cerr << "Warning: Failed to get target level in createRNGJsonFile. Stopping." << std::endl;
             break; // Stop if we can't find/create a level
        }
        nlohmann::json& parent = parentOpt.value().get();
        
        // Add a random key-value pair to this parent
         std::optional<std::pair<std::string, std::string>> newItemInfo; // Using string pair for logging
        if (!setRandomObject(parent, &newItemInfo)) { 
            std::cerr << "Warning: Failed to set random object in createRNGJsonFile. Stopping." << std::endl;
             break; 
        } 
        
        if(newItemInfo) {
           // Optional: Log the added item (can be verbose)
           // std::cout << "[" << ++itemCounter << "] Added Key: " << newItemInfo->first << ", Value: " << newItemInfo->second << " at depth ~" << targetDepth << std::endl;
        } else {
             ++itemCounter; // Still count the item addition
        }

        // Update size (dump can be expensive, consider alternative size estimation)
        currentSize = outputJson.dump().size(); 
    }

    // Save the final JSON
    if (!saveToFileImpl(path, 4, outputJson)) {
        std::cerr << "JSONHandler::createRNGJsonFile Error: Failed to save final RNG file to " << path << std::endl;
    }
}

// Template implementation needs to be in header or explicitly instantiated.
// Moving setRandomObject definition here (marked inline in header)
 template <typename T>
 inline bool JSONHandler::setRandomObject(nlohmann::json& parentObject, std::optional<T>* outputPair) const {
    if (!t_randomProvider) { return false; } // Need random provider
    if (!parentObject.is_object()) {
        return false; // Can only add key-value to objects
    }

    std::string currentKey;
    int tries = 0;
    do {
        currentKey = t_randomProvider->getRandomValue<std::string>({5, 15});
        if(++tries > 10) { // Avoid potential infinite loop if key generation is bad
             std::cerr << "Warning: Could not generate unique key in setRandomObject after 10 tries." << std::endl;
             // Fallback: Append a number?
             currentKey += std::to_string(t_randomProvider->getRandomValue<int>({0, 9999}));
             if(parentObject.contains(currentKey)) return false; // Give up
             break;
        }
    } while (parentObject.contains(currentKey));

    int value_type = t_randomProvider->getRandomValue<int>({1, 5}); // Include object type for nesting
    switch (value_type) {
        case 1: parentObject[currentKey] = t_randomProvider->getRandomValue<int>({0, 100}); break;
        case 2: parentObject[currentKey] = t_randomProvider->getRandomValue<float>({0.0f, 20.0f}); break;
        case 3: parentObject[currentKey] = t_randomProvider->getRandomValue<std::string>({10, 20}); break;
        case 4: parentObject[currentKey] = t_randomProvider->getRandomValue<bool>(); break;
        case 5: parentObject[currentKey] = nlohmann::json::object(); break; // Add empty object for nesting
        // case 6: parentObject[currentKey] = nlohmann::json::array(); break; // Add empty array
    }

    if (outputPair) {
        // This part needs careful handling based on T
        if constexpr (std::is_same_v<T, std::pair<std::string, std::string>>) {
            *outputPair = std::make_pair(currentKey, parentObject[currentKey].dump());
        } else { 
             // Handle other types of T if necessary, or static_assert(false)
             // For now, assume T is pair<string, string> if provided
        }
    }
    return true;
 }


std::optional<std::reference_wrapper<nlohmann::json>> JSONHandler::getRandomObject(nlohmann::json& parentObject) const {
    if (!t_randomProvider) { return std::nullopt; }
    if (!parentObject.is_object() || parentObject.empty()) {
        return std::nullopt;
    }

    std::vector<std::string> objectKeys;
    for (auto& [key, value] : parentObject.items()) {
        if (value.is_object()) {
            objectKeys.push_back(key);
        }
    }

    if (objectKeys.empty()) {
        return std::nullopt; // No sub-objects found
    }

    // Get a random index for the keys vector
    int keyIndex = t_randomProvider->getRandomValue<int>({0, static_cast<int>(objectKeys.size()) - 1});
    
    return std::ref(parentObject[objectKeys[keyIndex]]);
}

std::optional<std::reference_wrapper<nlohmann::json>> JSONHandler::getTargetLevel(nlohmann::json* rootKey, int targetDepth) const {
     if (!t_randomProvider) { return std::nullopt; }
     if (rootKey == nullptr || targetDepth < 0 || rootKey->is_null()) {
        return std::nullopt;
    }
    if (targetDepth == 0) { 
        return std::ref(*rootKey); // Target is the root itself
    }

    nlohmann::json* currentLevel = rootKey;
    for (int depth = 0; depth < targetDepth; ++depth) {
         if (!currentLevel->is_object()) { 
             // Cannot go deeper if current level is not an object
             // We could potentially convert it to an object, but let's return nullopt for now.
             return std::nullopt; 
         }

        std::optional<std::reference_wrapper<nlohmann::json>> nextLevelOpt = getRandomObject(*currentLevel);

        if (nextLevelOpt) {
            currentLevel = &nextLevelOpt.value().get();
        } else {
            // No existing sub-object to descend into, create a new one
            std::string newKey;
             int tries = 0;
             do {
                 newKey = t_randomProvider->getRandomValue<std::string>({5, 10}); // Shorter keys for nesting
                 if(++tries > 10) { 
                     newKey += std::to_string(t_randomProvider->getRandomValue<int>({0,999})); 
                     if(currentLevel->contains(newKey)) return std::nullopt; // Give up
                     break;
                 }
             } while (currentLevel->contains(newKey));

            (*currentLevel)[newKey] = nlohmann::json::object(); // Create a new object node
            currentLevel = &(*currentLevel)[newKey]; // Descend into the newly created node
        }
    }
    return std::ref(*currentLevel);
}


// --- Deep Key Existence Check Implementation ---
bool JSONHandler::findKeyRecursive(const std::string& key, const nlohmann::json& currentJson) const {
     if (currentJson.is_object()) {
        if (currentJson.contains(key)) {
            return true;
        }
        // Recurse into sub-objects and arrays
        for (const auto& item : currentJson.items()) {
            if (findKeyRecursive(key, item.value())) {
                return true;
            }
        }
    } else if (currentJson.is_array()) {
        for (const auto& element : currentJson) {
            if (findKeyRecursive(key, element)) {
                return true;
            }
        }
    }
    return false;
}

bool JSONHandler::containsKey(const std::string& key) const {
    // No lock needed if nlohmann::json reading methods are thread-safe
    return findKeyRecursive(key, m_jsonObject);
}
